///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.gaugesClasses
{
  import flash.utils.getDefinitionByName;
  
  import ilog.utils.CSSUtil;
  
  import mx.core.UIComponent;
  import mx.styles.CSSStyleDeclaration;

  /**
   *  Number of pixels between the component's left border
   *  and the left edge of its content area.
   *  
   *  @default 5
   */
  [Style(name="paddingLeft", type="Number", format="Length", inherit="no")]
  
  /**
   *  Number of pixels between the component's right border
   *  and the right edge of its content area.
   * 
   *  @default 5
   */
  [Style(name="paddingRight", type="Number", format="Length", inherit="no")]
  
  /**
   *  Specifies the number of pixels between the component's bottom border
   *  and its content area.
   * 
   *  @default 5
   */
  [Style(name="paddingBottom", type="Number", format="Length", inherit="no")]
  
  /**
   *  Specifies the number of pixels between the component's top border
   *  and its content area.
   * 
   *  @default 5
   */
  [Style(name="paddingTop", type="Number", format="Length", inherit="no")]
  
  /**
   *  Color of the text in the component, including the component label.
   *
   *  @default 0x0B333C
   */
  [Style(name="color", type="uint", format="Color", inherit="yes")]
  
  /**
   *  Color of the text in the component if it is disabled.
   *
   *  @default 0xAAB3B3
   */
  [Style(name="disabledColor", type="uint", format="Color", inherit="yes")]
  
  /**
   *  Sets the <code>antiAliasType</code> property of internal TextFields. The possible values are 
   *  <code>"normal"</code> (<code>flash.text.AntiAliasType.NORMAL</code>) 
   *  and <code>"advanced"</code> (<code>flash.text.AntiAliasType.ADVANCED</code>). 
   *  
   *  <p>The default value is <code>"advanced"</code>, which enables the FlashType renderer if you use an 
   *  embedded FlashType font. Set the value to <code>"normal"</code> to disable the FlashType renderer.</p>
   *  
   *  <p>This style does not affect system fonts.</p>
   *  
   *  <p>This style applies to the whole text in a TextField subcontrol; 
   *  you cannot apply it to some characters and not to others.</p>
  
   *  @default "advanced"
   * 
   *  @see flash.text.TextField
   *  @see flash.text.AntiAliasType
   */
  [Style(name="fontAntiAliasType", type="String", enumeration="normal,advanced", inherit="yes")]
  
  /**
   *  Name of the font to use.
   *  Unlike in a full CSS implementation,
   *  comma-separated lists are not supported.
   *  You can use any font family name.
   *  If you specify a generic font name,
   *  it is converted to an appropriate device font.
   * 
   *  @default "Verdana"
   */
  [Style(name="fontFamily", type="String", inherit="yes")]
  
  /**
   *  Sets the <code>gridFitType</code> property of internal TextFields that represent text in Flex controls.
   *  The possible values are <code>"none"</code> (<code>flash.text.GridFitType.NONE</code>), 
   *  <code>"pixel"</code> (<code>flash.text.GridFitType.PIXEL</code>),
   *  and <code>"subpixel"</code> (<code>flash.text.GridFitType.SUBPIXEL</code>). 
   *  
   *  <p>This property only applies when you use an embedded FlashType font 
   *  and the <code>fontAntiAliasType</code> property 
   *  is set to <code>"advanced"</code>.</p>
   *  
   *  <p>This style does not affect system fonts.</p>
   * 
   *  <p>This style applies to the whole text in a TextField subcontrol; 
   *  you cannot apply it to some characters and not to others.</p>
   * 
   *  @default "pixel"
   *  
   *  @see flash.text.TextField
   *  @see flash.text.GridFitType
   */
  [Style(name="fontGridFitType", type="String", enumeration="none,pixel,subpixel", inherit="yes")]
  
  /**
   *  Sets the <code>sharpness</code> property of internal TextFields that represent text in Flex controls.
   *  This property specifies the sharpness of the glyph edges. The possible values are Numbers 
   *  from -400 through 400. 
   *  
   *  <p>This property only applies when you use an embedded FlashType font 
   *  and the <code>fontAntiAliasType</code> property 
   *  is set to <code>"advanced"</code>.</p>
   *  
   *  <p>This style does not affect system fonts.</p>
   * 
   *  <p>This style applies to the whole text in a TextField subcontrol; 
   *  you cannot apply it to some characters and not others.</p>
   *  
   *  @default 0
   *  
   *  @see flash.text.TextField
   */
  [Style(name="fontSharpness", type="Number", inherit="yes")]
  
  /**
   *  Height of the text, in pixels.
   *  @default 10
   */
  [Style(name="fontSize", type="Number", format="Length", inherit="yes")]
  
  /**
   *  Determines whether the text is in the italic font.
   *  Allowed values are <code>"normal"</code> and <code>"italic"</code>.
   * 
   *  @default "normal"
   */
  [Style(name="fontStyle", type="String", enumeration="normal,italic", inherit="yes")]
  
  /**
   *  Sets the <code>thickness</code> property of internal TextFields that represent text in Flex controls.
   *  This property specifies the thickness of the glyph edges.
   *  The possible values are Numbers from -200 to 200. 
   *  
   *  <p>This property only applies when you use an embedded FlashType font 
   *  and the <code>fontAntiAliasType</code> property 
   *  is set to <code>"advanced"</code>.</p>
   *  
   *  <p>This style does not affect system fonts.</p>
   * 
   *  <p>This style applies to the whole text in a TextField subcontrol; 
   *  you cannot apply it to some characters and not others.</p>
   *  
   *  @default 0
   *  
   *  @see flash.text.TextField
   */
  [Style(name="fontThickness", type="Number", inherit="yes")]
  
  /**
   *  Determines whether the text is in boldface.
   *  Allowed values are <code>normal</code> and <code>bold</code>.
   *  The default value for Button controls is <code>bold</code>. 
   *  The default value for all other controls is <code>normal</code>.
   */
  [Style(name="fontWeight", type="String", enumeration="normal,bold", inherit="yes")]
  
  /**
   *  A Boolean value that indicates whether kerning
   *  is enabled (<code>true</code>) or disabled (<code>false</code>).
   *  Kerning adjusts the gap between certain character pairs
   *  to improve the readability. It should be used only when necessary,
   *  for example, with headings in large fonts.
   *  Kerning is supported for embedded FlashType fonts only. 
   *  Some fonts, like Verdana and monospaced fonts(for example,
   *  Courier New) do not support kerning.
   *
   *  @default false
   */
  [Style(name="kerning", type="Boolean", inherit="yes")]
  
  /**
   *  The number of additional pixels to display between each character.
   *  A positive value increases the character spacing beyond the normal spacing,
   *  while a negative value decreases it.
   * 
   *  @default 0
   */
  [Style(name="letterSpacing", type="Number", inherit="yes")]
  
  /**
   *  Alignment of text within a container.
   *  Possible values are <code>"left"</code>, <code>"right"</code>,
   *  and <code>"center"</code>.
   * 
   *  @default "left"
   */
  [Style(name="textAlign", type="String", enumeration="left,center,right", inherit="yes")]
  
  /**
   *  Determines whether the text is underlined.
   *  Possible values are <code>"none"</code> and <code>"underline"</code>.
   * 
   *  @default "none"
   */
  [Style(name="textDecoration", type="String", enumeration="none,underline", inherit="yes")]
  
  /**
   *  Offset, in pixels, of the first line of text from the left side of the container.
   * 
   *  @default 0
   */
  [Style(name="textIndent", type="Number", format="Length", inherit="yes")]

  /**
   *  Alpha level of the color defined by the <code>backgroundColor</code>
   *  property.
   *  Valid values range from 0.0 to 1.0.
   *  
   *  @default 1.0
   */
  [Style(name="backgroundAlpha", type="Number", inherit="no")]

  /**
   *  Background color of the component.
   * 
   *  @default undefined
   */
  [Style(name="backgroundColor", type="uint", format="Color", inherit="no")]

  /**
   *  Background color of the component when it is disabled.
   *  The global default value is <code>undefined</code>.
   * 
   *  @default undefined
   */
  [Style(name="backgroundDisabledColor", type="uint", format="Color", inherit="yes")]
  
  /**
   * Style declaration name for the text in the title bar.
   * The default value is <code>"windowStyles"</code>,
   * which causes the title to be in boldface.
   *
   * @default "windowStyles"
   * 
   */
  [Style(name="titleStyleName", type="String", inherit="no")]

  /**
   * Controls the visibility of the title label.
   *  
   * @default true
   */
  [Style(name="showTitle", type="Boolean", inherit="no")]

     

  [DefaultProperty("elements")]

  /**
   *  The <code>GaugeBase</code> class is a base class for the circular and 
   *  rectangular (horizontal and vertical) gauge types.
   *  <p>
   *  You typically do not use the <code>GaugeBase</code> class directly.
   *  Instead, you use one of its subclasses: <code>CircularGauge</code> or 
   *  <code>RectangularGauge</code>.
   *  </p>
   *  The ILOG Elixir gauges API defines a mechanism to construct gauges as 
   *  composite graphic objects. A gauge is made of two arrays:
   *  <ul>
   *    <li><i>scales</i>: An array of logical scales. ILOG Elixir provides default 
   *  common scales implementations such as linear and logarithmic scales or 
   *  category scales.</li>
   * 
   *    <li><i>elements</i>: An array of visual elements. Most of them end with 'Renderer' 
   *  and are subclasses of the <code>GaugeElement</code> class.
   *  Some elements can reference a scale for their rendering such 
   *  as <code>NeedleRenderer</code>, <code>CircularTrackRenderer</code>, or
   *  <code>MarkerRenderer</code>.</li>
   *  </ul>
   *   
   *  @mxml
   *  
   *  <p>The <code>&lt;ilog:GaugeBase&gt;</code> tag inherits all the
   *  properties of its parent classes and adds the following properties:</p>
   *  
   *  <pre>
   *  &lt;ilog:GaugeBase
   *    <strong>Styles</strong>  
   *    title=""
   *    titleGap="6"
   *    titlePlacement="bottom|top"* 
   *    backgroundColor="undefined"
   *    backgroundDisabledColor="undefined"
   *    backgroundAlpha="1.0" 
   *    paddingBottom="5"
   *    paddingLeft="5"
   *    paddingRight="5"
   *    paddingTop="5"
   *    color="0x000000"
   *    disabledColor="0xAAB3B3"
   *    fontAntiAliasType="advanced|normal"
   *    fontFamily="Verdana"
   *    fontGridFitType="pixel|none|subpixel"
   *    fontSharpness="0"
   *    fontSize="10"
   *    fontStyle="normal|italic"
   *    fontThickness="0"
   *    fontWeight="normal|bold"
   *    kerning="false"
   *    letterSpacing="0"
   *    textAlign="left|right|center"
   *    textDecoration="none|underline"
   *    textIndent="0"
   * /&gt;
   *  </pre>
   *  
   *  @see ilog.gauges.circular.CircularGauge
   *  @see ilog.gauges.rectangular.RectangularGauge
   */
  public class GaugeBase extends UIComponent
  {
    private static var stylesInited:Boolean = initStyles(); 

    private static function initStyles():Boolean
    {
      var style:CSSStyleDeclaration = CSSUtil.createSelector("GaugeBase");   
            
      style.defaultFactory = function():void {
        this.showTitle = true;
        this.titleStyleName = "windowStyles";    
        this.paddingLeft = 5;
        this.paddingRight = 5;
        this.paddingTop = 5;
        this.paddingBottom = 5;
        // TODO document
        this.fontSize = 20;
        this.textAlign = "center";        
      } 
      
      return true;
    }
    
    private var _elements:Array = [];
    private var _elementsDirty:Boolean;    

    /**
     *  Constructor.
     */           
    public function GaugeBase()
    {
      super();
      
    }

    private var _title:String = null;
    
    [Inspectable(category="Gauge")]    
       
    /**
     * The title of the gauge to be displayed at the top or bottom of the gauge
     * depending on the <code>titlePlacement</code> property. If <code>null</code>,
     * no title is displayed.
     * 
     * @default null
     * @see #titlePlacement
     * @see #titleGap
     */           
    public function get title():String
    {
      return _title;
    }
    
    /**
     * @private
     */
    public function set title(value:String):void 
    {
      _title = value;   
    }
      
    private var _titlePlacement:String;
    
    [Inspectable(category="Gauge", defaultValue="bottom", enumeration="bottom,top", type="String")]             
    
    /**
     * The placement of the title. If <code>"bottom"</code>, the title is displayed
     * at the bottom of the gauge. If <code>"top"</code>, the title is displayed
     * at the top of the gauge.
     * 
     * @default "bottom"
     * @see #title
     */
    public function get titlePlacement():String
    {
      return _titlePlacement;  
    }
    
    /**
     * @private
     */
    public function set titlePlacement(value:String):void 
    {
      _titlePlacement = value;
      invalidateDisplayList();     
    }   
    
    private var _titleGap:Number = 6;
    
    [Inspectable(category="Gauge")]   
        
    /**
     * The gap between the title and the rest of the gauge.
     * 
     * @default 6
     * @see #title
     */
    public function get titleGap():Number
    {
      return _titleGap;  
    }
    
    /**
     * @private
     */
    public function set titleGap(value:Number):void 
    {
      _titleGap = value;
      invalidateDisplayList();     
    } 

    [Inspectable(category="Gauge", arrayType="ilog.gauges.gaugesClasses.GaugeElement")]

    /**
     * An Array of gauge visual elements such as scale renderers, needles, or markers. 
     * These elements are subclasses of <code>GaugeElement</code>.
     * Elements are added to the display list in the same order as in the array.
     * 
     * @default []
     */
    public function get elements():Array
    {
      return _elements;
    }

    /**
     *  @private
     */
    public function set elements(value:Array):void
    {
      value = (value == null) ? [] : value;
      _elements = value;
      invalidateChildren();
    }
    
    /**
     * This method is called when the elements array is modified.
     */
    public function invalidateChildren():void
    {
      _elementsDirty = true;
      invalidateProperties(); 
    }

    private var _scales:Array = [];
    
    [Inspectable(category="Gauge", arrayType="ilog.gauges.gaugesClasses.IScale")]

    /**
     * An Array of logical scales. There are specific scale classes for circular 
     * and rectangular gauges. Circular scales implement the <code>ICircularScale</code>
     * interface and rectangular scales implement the <code>IRectangularScale</code>
     * interface. A scale maps a value to an angle or to a position. It is used by 
     * some graphic elements, like subclasses of <code>CircularScaleRelatedRenderer</code>
     * or <code>RectangularScaleRelatedRenderer</code>, to update themselves. 
     * 
     * @default [] 
     * 
     * @see ilog.gauges.gaugesClasses.circular.CircularScaleRelatedRenderer
     * @see ilog.gauges.gaugesClasses.rectangular.RectangularScaleRelatedRenderer
     */
    public function get scales():Array
    {
      return _scales;
    }

    /**
     *  @private
     */
    public function set scales(value:Array):void
    {
      // if we are in auto discovery we need to invalidate all the 
      // elements that relates to the first scale because they will
      // relate to a new scale now on.
      var autoScale:IScale = null;
      if (_scales.length > 0) {
        autoScale = _scales[0];
      }
      _scales = value;
      if (autoScale != null)
        autoScale.invalidateElements();
    }

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
      super.commitProperties();
      if (_elementsDirty) {
        var mainHolder:UIComponent = getElementHolder(null);
        while (mainHolder.numChildren != 0)
          mainHolder.removeChildAt(0);          
        if (_elements) {
          for each (var element:GaugeElement in _elements) {
            // even if we add to an intermediate layer
            // our owner is the gauge
            element.owner = this; 
            getElementHolder(element).addChild(element);            
          }
        }      
        _elementsDirty = false;
        // let's invalidate display as we might want to re-arrange the
        // children
        invalidateDisplayList();
      }
    }    
        
    /**
     * @private
     */
    protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);
      var backgroundColor:Object = enabled ?
        null :
        getStyle("backgroundDisabledColor");

      if (backgroundColor == null)
        backgroundColor = getStyle("backgroundColor");
      
      var backgroundAlpha:Number = getStyle("backgroundAlpha");
      
      graphics.clear();
      
      if (backgroundColor != null) {
        graphics.beginFill(uint(backgroundColor), backgroundAlpha);
        graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
        graphics.endFill();
      }
    }
    
    /**
     * This method returns the <code>UIComponent</code> into which the given
     * gauge element must be added. By default it returns the gauge itself.
     * 
     * @param element the gauge element to be added
     * @return the component in which it must be added
     */
    protected function getElementHolder(element:GaugeElement):UIComponent
    {
      return this;
    }

    /**
     *  @private
     */
    override protected function measure():void
    {
      super.measure();
      measuredMinWidth = 50;
      measuredMinHeight = 50;
      measuredWidth = 200;
      measuredHeight = 200;
    }    
  }       
}
